package core.net;

import core.buffer.Buffer;
import core.buffer.BufferManager;
import core.net.protocol.Packet;
import net.PoolExecutor;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

public class Connection {

    private boolean _enable;
    private Socket _socket;
    private Packet _packet;
    private Buffer _buffer;
    private InetSocketAddress _localAddress;
    private InetSocketAddress _remoteAddress;
    private Consumer<Buffer> _receiveHandle;
    private Consumer<Connection> _disconnectHandle;

    public boolean enable()
    {
        return _enable;
    }

    public void receiveHandler(Consumer<Buffer>  handler)
    {
        _receiveHandle = handler;
    }

    public void disconnectHandler(Consumer<Connection>  handler)
    {
        _disconnectHandle = handler;
    }

    public Connection(Socket socket, BufferManager manager, NetConfig config) {
        _socket = socket;
        _enable = true;
        _buffer = manager.apply(config.ReceiveBufferSize());
        _packet = new Packet(manager);
        _localAddress = (InetSocketAddress) socket.getLocalSocketAddress();
        _remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
        receiveHandler(this::received);
        disconnectHandler(this::disconnected);
    }

    public void open() {
        if (!_enable) return;
        System.out.println("[Connect Open] local " + _localAddress + "  remote " + _remoteAddress);
        receive();
    }

    public void write(Buffer data) {
        if (!_enable){
            System.out.println("write fail! connect is closed!");
            return;
        }
        CompletableFuture.runAsync(() -> {
            try {
                OutputStream os = _socket.getOutputStream();
                data.read(os);
            } catch (IOException e) {
                System.out.println(e.getMessage());
                close();
            }
        }, PoolExecutor.executor());
    }

    private void receive() {
        CompletableFuture.runAsync(()->{
            try {
                _buffer.clear();
                int readCount = _buffer.write(_socket.getInputStream());
                if (readCount > 0) {
                    _packet.unpack(_buffer, _receiveHandle);
                    receive();
                } else {
                    close();
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
                close();
            }
        });
    }

    private void received(Buffer data) {
        System.out.println("[Receive] from " + _remoteAddress + " count " + data.readRemaining());
    }

    private void disconnected(Connection connection) {

    }

    public void close() {

        if (!_enable) return;

        try {
            _enable = false;
            _packet.dispose();
            _buffer.clear();
            _socket.close();
            _disconnectHandle.accept(this);
            receiveHandler(null);
            disconnectHandler(null);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("[Close] local " + _localAddress + "  remote " + _remoteAddress);
    }

}
